-
Notifications
You must be signed in to change notification settings - Fork 93
implement get_lineage tool for complete lineage across dbt resources #461
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
DevonFulcher
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work!
src/dbt_mcp/discovery/client.py
Outdated
| } | ||
| """) | ||
|
|
||
| GET_SEEDS = textwrap.dedent(""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just merged a PR that adds GQL queries for seeds & snapshots. Can we reuse those queries here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the new GQL folder pattern, I have placed the GQLs in a folder, but I dont think we can reuse the existing GQLs. The reason is because the queries use resources() with AppliedResourcesFilter, which requires knowing the unique_id upfront (via the uniqueIds array).
The new search gql queries we created for the lineage are lightweight searches using the specific seeds()/snapshots() endpoints with GenericMaterializedFilter's identifier field for name-based matching.
Through introspection, I noticed that GenericMaterializedFilter has native identifier support for name matching, so we use that for 1-call lookups instead of the package enumeration pattern.
src/dbt_mcp/discovery/tools.py
Outdated
| direction: str = "both", | ||
| types: list[str] | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better if these were enums or some other stronger type than strings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I have Implemented this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I still see that it is a string type and a list of strings. I think these would be better as an enum and list of enums:
direction: str = "both",
types: list[str] | None = None,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh sorry forgot that on the refactor, I have switched it back to using enums.
src/dbt_mcp/discovery/tools.py
Outdated
| matches = await context.lineage_fetcher.search_all_resources(name) | ||
| if not matches: | ||
| raise InvalidParameterError( | ||
| f"No resource found with name '{name}' in searchable resource types " | ||
| f"(models, sources, seeds, snapshots).\n\n" | ||
| f"If this is an exposure, test, or metric, you must use the full unique_id instead:\n" | ||
| f" • For exposures: get_lineage(unique_id='exposure.project.{name}')\n" | ||
| f" • For tests: get_lineage(unique_id='test.project.{name}')\n" | ||
| f" • For metrics: get_lineage(unique_id='metric.project.{name}')\n\n" | ||
| f"Note: The Discovery API does not support searching exposures, tests, or metrics by name. " | ||
| f"You can find unique IDs in your dbt Cloud project or manifest.json." | ||
| ) | ||
| if len(matches) == 1: | ||
| resolved_unique_id = matches[0]["uniqueId"] | ||
| else: | ||
| # Multiple matches - try elicitation first, fallback to disambiguation | ||
| try: | ||
| # Format matches for display | ||
| match_descriptions = [ | ||
| f"{m['resourceType']}: {m['uniqueId']}" for m in matches | ||
| ] | ||
| message = ( | ||
| f"Multiple resources found with name '{name}':\n" | ||
| + "\n".join(f" {i+1}. {desc}" for i, desc in enumerate(match_descriptions)) | ||
| + "\n\nSelect the unique_id of the resource you want:" | ||
| ) | ||
|
|
||
| result = await ctx.elicit( | ||
| message=message, | ||
| schema=ResourceSelection | ||
| ) | ||
|
|
||
| if result.action == "accept": | ||
| # Validate the selected unique_id is in matches | ||
| selected_id = result.data.unique_id | ||
| if selected_id in [m["uniqueId"] for m in matches]: | ||
| resolved_unique_id = selected_id | ||
| else: | ||
| raise InvalidParameterError( | ||
| f"Selected unique_id '{selected_id}' not in available matches" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty interesting! How do you think it compares to the approach I took in my recent changes here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually came up with this pattern after introspection of the Discovery API since ModelAppliedFilter, SourceAppliedFilter, and GenericMaterializedFilter all have an identifier field for name-based searching, I just used that native support instead which means we can just make fewer calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think either approach works well, and I feel okay with trying this out. It will be our first use of elicitation, so that is exciting! It is a creative solution. Thanks for that.
At some point, we should consolidate our approaches, though. I would like the Discovery tools to work uniformly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm yeah definitely, I think we should be able to get elicitation to work with either approaches. Question for you then can we assume the approach here would be the standard going forward https://github.com/dbt-labs/dbt-mcp/blob/main/src/dbt_mcp/discovery/client.py#L625-L650?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the approach could be: try elicitation. If the client doesn't support elicitation, fall back to the method you linked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point is that this could be the method for all discovery tools, not just lineage. That uniformity doesn't have to be done in this PR, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alright I will just raise a separate issue to standardize the approach for discovery tools
| ) | ||
| descendants_result = await self._fetch_lineage_single_direction( | ||
| unique_id, LineageDirection.DESCENDANTS, types | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the reason for the two API calls to simplify labeling ancestors and descendants? The selector syntax allows for both directions here, but I assume the separate calls make it easier to label the nodes. Is that right?
One suggestion, if we keep two separate calls, they can be done concurrently with asyncio.gather().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup! the reason for the two calls is so we can group them into ancestors vs descendants. When using +uniqueId+, the API returns everything mixed together, so we'd need extra logic to figure out which nodes are uancestors vs descendants. I have implemented the asyncio.gather() for parallel execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a new get_lineage tool that retrieves complete upstream and downstream lineage for dbt resources in a single API call, addressing user feedback about needing multiple calls to traverse the lineage graph. The implementation includes name-based search with disambiguation support and fallback mechanisms for resource types that require unique IDs.
Key Changes:
- New
LineageFetcherclass handles resource search and lineage retrieval across models, sources, seeds, and snapshots - Interactive disambiguation via MCP elicitation when multiple resources match a name
- Client-side pagination limiting results to 50 nodes per direction
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/dbt_mcp/discovery/client.py |
Adds LineageFetcher class with search and lineage fetching capabilities, including GraphQL queries for seeds, snapshots, and lineage |
src/dbt_mcp/discovery/tools.py |
Implements get_lineage tool with parameter validation, name resolution, and elicitation-based disambiguation |
src/dbt_mcp/prompts/discovery/get_lineage.md |
Documents tool usage, parameters, limitations, and examples |
src/dbt_mcp/tools/tool_names.py |
Registers new GET_LINEAGE tool name |
src/dbt_mcp/tools/toolsets.py |
Adds GET_LINEAGE to discovery toolset |
tests/unit/discovery/test_lineage_fetcher.py |
Tests for resource search, selector building, lineage fetching, and pagination |
tests/unit/discovery/test_get_lineage_tool.py |
Tests for parameter validation, name resolution, and disambiguation flow |
tests/unit/discovery/conftest.py |
Adds fixtures for LineageFetcher and mock contexts |
.changes/unreleased/Enhancement or New Feature-20251129-121002.yaml |
Changelog entry for the new feature |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "matches": matches, | ||
| "instruction": "Please call get_lineage again with the unique_id parameter set to one of the matches above.", | ||
| } | ||
|
|
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type ignore comment suggests that resolved_unique_id could potentially be None at runtime. Since the code paths ensure it's always assigned (either from unique_id parameter, single match, or elicitation), consider adding an assertion to make this guarantee explicit: assert resolved_unique_id is not None before line 291.
| assert resolved_unique_id is not None, "resolved_unique_id must not be None at this point" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| {"uniqueId": "source.test.raw.customers", "name": "customers", "resourceType": "Source"}, | ||
| ] | ||
| # Simulate elicitation failure | ||
| mock_mcp_context.elicit.side_effect = Exception("Elicitation not supported") |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test simulates a generic Exception, but the actual code catches all exceptions at line 281 in tools.py. Consider testing with more specific exception types (e.g., TimeoutError, ConnectionError) to ensure the fallback behavior works correctly for different failure scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/dbt_mcp/discovery/client.py
Outdated
| "first": PAGE_SIZE, | ||
| } | ||
|
|
||
| # Execute query - already a direct reference to the query string |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The comment on line 1026 'already a direct reference to the query string' is unclear about why this reassignment is necessary. Consider clarifying that this extracts the GraphQL query string from the configuration dictionary for readability.
| # Execute query - already a direct reference to the query string | |
| # Extract the GraphQL query string from the configuration dictionary for readability |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment clarified added in branch
src/dbt_mcp/discovery/tools.py
Outdated
| # Format matches for display | ||
| match_descriptions = [ | ||
| f"{m['resourceType']}: {m['uniqueId']}" for m in matches | ||
| ] | ||
| message = ( | ||
| f"Multiple resources found with name '{name}':\n" | ||
| + "\n".join(f" {i+1}. {desc}" for i, desc in enumerate(match_descriptions)) | ||
| + "\n\nSelect the unique_id of the resource you want:" |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The message construction spans multiple operations (list comprehension, string concatenation, enumeration). Consider extracting this into a helper method _format_disambiguation_message(name: str, matches: list[dict]) -> str to improve readability and testability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added _format_disambiguation_message
461f5fc to
62cf578
Compare
src/dbt_mcp/discovery/client.py
Outdated
| # ============================================================================ | ||
| # Lineage Tool Classes and Configuration | ||
| # ============================================================================ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than a demarcation like this, should we move this to a different file? It seems we could break this file up a bit more in general.
src/dbt_mcp/discovery/tools.py
Outdated
| logger.debug(f"Elicitation not completed: {type(e).__name__}: {e}") | ||
| return { | ||
| "status": "disambiguation_required", | ||
| "message": f"Multiple resources found with name '{name}'", | ||
| "matches": matches, | ||
| "instruction": "Please call get_lineage again with the unique_id parameter set to one of the matches above.", | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should handle this situation in a different way. Not all clients support elicitation, so we should fall back to a different method. Perhaps we should return a list of trees? Additionally, are you familiar with how elicitation is handled with remote MCP? We should ensure this tool is compatible with remote MCP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I will remove elicitation for now as I am not sure how it would work in remote mcp, I will create a different issue to look into this approach.
|
Hi @DevonFulcher I just made some changes to align better, I will raise the issues for discovery api uniformity and exploring elicitation after this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,3 @@ | |||
| kind: Enhancement or New Feature | |||
| body: Adds Get_lineage tool for getting lineage across resources. | |||
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent capitalization in tool name - should be get_lineage (lowercase with underscore) to match the actual tool name defined in ToolName.GET_LINEAGE = "get_lineage".
| body: Adds Get_lineage tool for getting lineage across resources. | |
| body: Adds get_lineage tool for getting lineage across resources. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| def _fix_lineage_annotations(): | ||
| """Replace string annotations with actual DiscoveryToolContext class.""" | ||
| from dbt_mcp.discovery.tools import DiscoveryToolContext | ||
|
|
||
| # Fix get_lineage annotations | ||
| get_lineage.fn.__annotations__['context'] = DiscoveryToolContext | ||
|
|
||
| # Fix _fetch_all_lineage_trees annotations | ||
| _fetch_all_lineage_trees.__annotations__['context'] = DiscoveryToolContext |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function mutates __annotations__ dictionaries at runtime to work around circular imports. This is fragile and could break with Python version updates or type checkers. Consider restructuring the imports (e.g., using a protocol/interface, dependency injection, or moving shared types to a separate module) to avoid circular dependencies entirely rather than patching annotations at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted the get_lineage tools to single file approach to avoid this circular dependency, this will be addressed in future discovery API uniformity refactoring.
| packages_result = await self.api_client.execute_query( | ||
| ResourceDetailsFetcher.GET_PACKAGES_QUERY, | ||
| variables={"resource": "model", "environmentId": environment_id}, | ||
| ) |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on line 765 states "Using 'model' resource for package fetching works for all types" but doesn't explain why this is the case. Add a brief explanation of why the "model" resource can be used for all resource types, as this is non-obvious and important for maintainability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed comments
| """Should raise ValueError for invalid direction.""" | ||
| with pytest.raises(ValueError, match="Invalid direction"): | ||
| lineage_fetcher._build_selector( | ||
| "model.jaffle_shop.customers", "invalid_direction" | ||
| ) |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test passes a string "invalid_direction" but _build_selector expects a LineageDirection enum. Consider also testing with None and other invalid types to ensure proper type validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests for None, int, list, dict, and empty string types
| normalized_name = name.strip().lower() if name else None | ||
| normalized_unique_id = unique_id.strip().lower() if unique_id else None |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normalizing unique_id to lowercase may cause issues if the API expects case-sensitive unique IDs. Verify that the Discovery API handles unique IDs case-insensitively, or document why this normalization is safe. If uncertain, consider preserving the original case for unique_id while still normalizing name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Discovery API handles unique IDs case-insensitively so this is fine.
9d3ce3d to
819626c
Compare
Bump mcp from 1.14.1 to 1.23.0 in /examples/aws_strands_agent (dbt-labs#465) Bumps [mcp](https://github.com/modelcontextprotocol/python-sdk) from 1.14.1 to 1.23.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/modelcontextprotocol/python-sdk/releases">mcp's releases</a>.</em></p> <blockquote> <h2>v1.23.0</h2> <h2>Summary</h2> <p>This release brings us up to speed with the latest MCP spec <code>2025-11-25</code>. Take a look at the <a href="https://modelcontextprotocol.io/specification/2025-11-25">latest spec</a> as well as the release <a href="https://blog.modelcontextprotocol.io/posts/2025-11-25-first-mcp-anniversary/">blog post.</a></p> <h2>What's Changed</h2> <ul> <li>Add tests for JSON Schema 2020-12 field preservation (SEP-1613) by <a href="https://github.com/felixweinberger"><code>@felixweinberger</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1649">modelcontextprotocol/python-sdk#1649</a></li> <li>Add client_secret_basic authentication support by <a href="https://github.com/jonshea"><code>@jonshea</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1334">modelcontextprotocol/python-sdk#1334</a></li> <li>Implement SEP-1577 - Sampling With Tools by <a href="https://github.com/ochafik"><code>@ochafik</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1594">modelcontextprotocol/python-sdk#1594</a></li> <li>SEP-1330: Elicitation Enum Schema Improvements and Standards Compliance by <a href="https://github.com/chughtapan"><code>@chughtapan</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1246">modelcontextprotocol/python-sdk#1246</a></li> <li>[auth][conformance] add conformance auth client by <a href="https://github.com/pcarleton"><code>@pcarleton</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1640">modelcontextprotocol/python-sdk#1640</a></li> <li>Implement SEP-986: Tool name validation by <a href="https://github.com/felixweinberger"><code>@felixweinberger</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1655">modelcontextprotocol/python-sdk#1655</a></li> <li>fix: url for spec by <a href="https://github.com/felixweinberger"><code>@felixweinberger</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1659">modelcontextprotocol/python-sdk#1659</a></li> <li>feat: implement SEP-991 URL-based client ID (CIMD) support by <a href="https://github.com/pcarleton"><code>@pcarleton</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1652">modelcontextprotocol/python-sdk#1652</a></li> <li>Update doc string on custom_route by <a href="https://github.com/pcarleton"><code>@pcarleton</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1660">modelcontextprotocol/python-sdk#1660</a></li> <li>Implement SEP-1036: URL mode elicitation for secure out-of-band interactions by <a href="https://github.com/cbcoutinho"><code>@cbcoutinho</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1580">modelcontextprotocol/python-sdk#1580</a></li> <li>Skip empty SSE data to avoid parsing errors by <a href="https://github.com/felixweinberger"><code>@felixweinberger</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1670">modelcontextprotocol/python-sdk#1670</a></li> <li>SEP-1686: Tasks by <a href="https://github.com/maxisbey"><code>@maxisbey</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1645">modelcontextprotocol/python-sdk#1645</a></li> <li>Add on_session_created callback option by <a href="https://github.com/crondinini-ant"><code>@crondinini-ant</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1710">modelcontextprotocol/python-sdk#1710</a></li> <li>Add SSE polling support (SEP-1699) by <a href="https://github.com/felixweinberger"><code>@felixweinberger</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1654">modelcontextprotocol/python-sdk#1654</a></li> <li>Support client_credentials flow with JWT and Basic auth by <a href="https://github.com/pcarleton"><code>@pcarleton</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1663">modelcontextprotocol/python-sdk#1663</a></li> <li>feat: backwards-compatible create_message overloads for SEP-1577 by <a href="https://github.com/felixweinberger"><code>@felixweinberger</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1713">modelcontextprotocol/python-sdk#1713</a></li> <li>Auto-enable DNS rebinding protection for localhost servers by <a href="https://github.com/pcarleton"><code>@pcarleton</code></a> (d3a184119e4479ea6a63590bc41f01dc06e3fa99)</li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/ochafik"><code>@ochafik</code></a> made their first contribution in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1594">modelcontextprotocol/python-sdk#1594</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/modelcontextprotocol/python-sdk/compare/v1.22.0...v1.23.0">https://github.com/modelcontextprotocol/python-sdk/compare/v1.22.0...v1.23.0</a></p> <h2>v1.22.0</h2> <h2>What's Changed</h2> <ul> <li>feat: Pass through and expose additional parameters in <code>ClientSessionGroup.call_tool</code> and <code>.connect_to_server</code> by <a href="https://github.com/inaku-Gyan"><code>@inaku-Gyan</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1576">modelcontextprotocol/python-sdk#1576</a></li> <li>chore: Lazy import <code>jsonschema</code> library by <a href="https://github.com/wuliang229"><code>@wuliang229</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1596">modelcontextprotocol/python-sdk#1596</a></li> <li>docs: Update examples to use stateless HTTP with JSON responses by <a href="https://github.com/domdomegg"><code>@domdomegg</code></a> in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1499">modelcontextprotocol/python-sdk#1499</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/wuliang229"><code>@wuliang229</code></a> made their first contribution in <a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1596">modelcontextprotocol/python-sdk#1596</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/modelcontextprotocol/python-sdk/compare/v1.21.1...v1.22.0">https://github.com/modelcontextprotocol/python-sdk/compare/v1.21.1...v1.22.0</a></p> <h2>v1.21.2</h2> <h2>Hotfix Release</h2> <p>This is a hotfix release to address a critical bug in OAuth scope handling that caused failures on 401 responses.</p> <h3>Related:</h3> <ul> <li><a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1630">modelcontextprotocol/python-sdk#1630</a></li> <li><a href="https://redirect.github.com/modelcontextprotocol/python-sdk/pull/1632">modelcontextprotocol/python-sdk#1632</a></li> </ul> <h2>What's Changed</h2> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/d3a184119e4479ea6a63590bc41f01dc06e3fa99"><code>d3a1841</code></a> Merge commit from fork</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/fa851d93a2036a37cce73e098f7dbc80a6c48765"><code>fa851d9</code></a> feat: backwards-compatible create_message overloads for SEP-1577 (<a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1713">#1713</a>)</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/f82b0c937178815c1e96460455778578050c6d1a"><code>f82b0c9</code></a> Support client_credentials flow with JWT and Basic auth (<a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1663">#1663</a>)</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/281fd4765e0fc2efaf2039d248c3bc0698416a8a"><code>281fd47</code></a> Add SSE polling support (SEP-1699) (<a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1654">#1654</a>)</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/2cd178a962ab454e3add228ecd721784b7b36e99"><code>2cd178a</code></a> Add on_session_created callback option (<a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1710">#1710</a>)</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/c92bb2f7ffaa61813d7cc350887f4ece38307769"><code>c92bb2f</code></a> SEP-1686: Tasks (<a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1645">#1645</a>)</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/5983a650cc07d2dc6c6ba098e99d3545889157a9"><code>5983a65</code></a> Skip empty SSE data to avoid parsing errors (<a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1670">#1670</a>)</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/02b78899296ce3631565345501e3d956b83ffe94"><code>02b7889</code></a> Implement SEP-1036: URL mode elicitation for secure out-of-band interactions ...</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/27279bc157cbc03f7fe7758fd55a4b34c5652f42"><code>27279bc</code></a> Update doc string on custom_route (<a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1660">#1660</a>)</li> <li><a href="https://github.com/modelcontextprotocol/python-sdk/commit/f22501315eab5b8358c603ac7f730f77bb09e4c4"><code>f225013</code></a> feat: implement SEP-991 URL-based client ID (CIMD) support (<a href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/1652">#1652</a>)</li> <li>Additional commits viewable in <a href="https://github.com/modelcontextprotocol/python-sdk/compare/v1.14.1...v1.23.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/dbt-labs/dbt-mcp/network/alerts). </details> Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> conflict-resolve fix-rebase refactor-long-chain revert-change revert-change add-refactored-code refactor-RESOURCE_SEARCH_CONFIG remove-old-configs add-new-fixez add-new-fixez add-changie rm-gitignore fix-pr-comments add-aysnc-query fix-copilot refactor-remove-eliciation refactor-remove-eliciation fixed done done copilot-query-1 remove-comments add-more-testing-as-per-copilot-suggestions revert-to-single-file fix-comments
39c19e9 to
17d9584
Compare
5ed45fc to
57dd669
Compare
Summary
A User building a catalog-like experience with dbt and Snowflake via Claude Desktop requested full lineage capabilities #110:
What Changed
Discovery Phase: GraphQL Introspection
We used GraphQL introspection to validate design decisions and understand the Discovery API's capabilities.
Why we used Sequential Search Strategy
Question: Does
LineageFiltersupport searching by name/identifier? LineageFilterFinding: -
LineageFilteronly accepts full unique IDs (noidentifierornamefield). Due to this Sequential search across resource types is necessary.Two API Calls for "both" Direction
Question: Does
ModelLineageNodeinclude direction metadata to categorize ancestors vs descendants?View ModelLineageNode Introspection
Finding: - No direction metadata exists (no
relationshipDirection,isAncestor,isDescendant, ordependsOnfields). Two separate API calls required for "both" direction.Graphql Client-Side Pagination
Question: Does the
lineagequery support server-side pagination parameters?Introspect
Finding: - No pagination parameters available (no
first,after,offset, orlimitarguments). Client-side 50-node limit is necessary.List Of Resource Search Support
Question: Which resource types support searching by name (via
identifierfield)?link Introspection
Finding: - 4 out of 7 resource types support name search:
ModelAppliedFilter.identifier)SourceAppliedFilter.identifier)GenericMaterializedFilter.identifier)GenericMaterializedFilter.identifier)identifierfield)identifierfield)Design Approach
Complete Resource Search Coverage
Based on introspection findings:
identifier?ModelAppliedFilterSourceAppliedFilterGenericMaterializedFilterGenericMaterializedFilterExposureFilterTestAppliedFilterMetricFilterKey Design Decisions
get_lineagetool)+uniqueIdanduniqueId+calls to categorize ancestors vs descendantsunique_idformat examples for their input (e.g.,exposure.project.{name}), (4) Explains API limitation so users understand whyCritical Test Cases
1. Disambiguation Flow (Which is one of the Important UX Feature in this change). This model was selected because it will trigger eliciation.
Prompt:
Use mcp inspector to force elicitation


2. Prompt like Error Message (Resource Type Limitation Handling)
Prompt:
3. Full Lineage (Feature Completeness)
Prompt:
Why
Implementing to close #110
Related Issues
Closes ##110
Related to ##110
Checklist
Additional Notes
Related Issues
Closes #
Related to #
Checklist
Additional Notes